﻿using System;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace XNASpriteLib
{
	public class SpriteAnimator
	{
		private readonly Sprite sprite;

		private int curSet;
		private int curAnimation;
		private int curOrientation;
		private int curFrameIndex;

		private TimeSpan timeSinceLastFrame = new TimeSpan(0);


		/// <summary>
		/// Creates a new animator for the given sprite
		/// </summary>
		/// <param name="sprite">The sprite to animate</param>
		public SpriteAnimator(Sprite sprite)
		{
			this.sprite = sprite;
		}


		/// <summary>
		/// Gets or Sets the current Set. Note that changing this will cause neither the frame index nor the animation to change.
		/// </summary>
		public String CurrentSet
		{
			get
			{
				return sprite.Set[curSet].Name;
			}
			set
			{
				if (sprite.SetLookup.ContainsKey(value))
				{
					curSet = sprite.SetLookup[value];
				}
			}
		}


		/// <summary>
		/// Gets or Sets the current Animation. Note that changing this will cause the frame index to be reset.
		/// </summary>
		public String CurrentAnimation
		{
			get
			{
				return sprite.Animation[curAnimation].Name;
			}
			set
			{
				if (sprite.AnimationLookup.ContainsKey(value))
				{
					curAnimation = sprite.AnimationLookup[value];

					// restart the animation
					curFrameIndex = 0;
				}
			}
		}


		/// <summary>
		/// Gets or Sets the current Orientation. Note that changing this will cause neither the frame index nor the animation to change.
		/// </summary>
		public String CurrentOrientation
		{
			get
			{
				return sprite.Orientation[curOrientation].Name;
			}
			set
			{
				if (sprite.OrientationLookup.ContainsKey(value))
				{
					curOrientation = sprite.OrientationLookup[value];
				}
				else
				{
					// Orientation not found!
					Debugger.Break();
				}
			}
		}


		/// <summary>
		/// Draw this sprite
		/// </summary>
		/// <param name="spriteBatch">The sprite batch to draw to</param>
		/// <param name="center">The center of the sprite</param>
		public void Draw(SpriteBatch spriteBatch, Vector2 center)
		{
			Draw(spriteBatch, center, 0, 1, Color.White);
		}


		/// <summary>
		/// Draw this sprite using a tint
		/// </summary>
		/// <param name="spriteBatch">The sprite batch to use</param>
		/// <param name="center">The center of the sprite</param>
		/// <param name="tint">The color to tint the sprite, use Color.White for full color with no tint</param>
		public void Draw(SpriteBatch spriteBatch, Vector2 center, Color tint)
		{
			Draw(spriteBatch, center, 0, 1, tint);
		}


		/// <summary>
		/// Draw this sprite using a tint
		/// </summary>
		/// <param name="spriteBatch">The sprite batch to use</param>
		/// <param name="center">The center of the sprite</param>
		/// <param name="angle">Specifies the angle (in radians) to rotate the sprite about its center</param>
		/// <param name="tint">The color to tint the sprite, use Color.White for full color with no tint</param>
		public void Draw(SpriteBatch spriteBatch, Vector2 center, float angle, Color tint)
		{
			Draw(spriteBatch, center, angle, 1, tint);
		}


		/// <summary>
		/// Draw this sprite using a tint
		/// </summary>
		/// <param name="spriteBatch">The sprite batch to use</param>
		/// <param name="center">The center of the sprite</param>
		/// <param name="angle">Specifies the angle (in radians) to rotate the sprite about its center</param>
		/// <param name="scale">Scale factor</param>
		/// <param name="tint">The color to tint the sprite, use Color.White for full color with no tint</param>
		public void Draw(SpriteBatch spriteBatch, Vector2 center, float angle, float scale, Color tint)
		{
			Frame currFrame = sprite.Frame[curSet, curAnimation, curOrientation][curFrameIndex];
			spriteBatch.Draw(currFrame.Texture,
							 center,
			                 currFrame.Rect,
			                 tint,
							 angle,
							 currFrame.Anchor,
							 scale,
							 SpriteEffects.None,
							 0);
		}


		/// <summary>
		/// Gets the texture that the current frame resides on
		/// </summary>
		public Texture2D FrameTexture
		{
			get
			{
				return sprite.Frame[curSet, curAnimation, curOrientation][curFrameIndex].Texture;
			}
		}


		/// <summary>
		/// Gets the current frame's bounds
		/// </summary>
		public Rectangle FrameRect
		{
			get
			{
				return sprite.Frame[curSet, curAnimation, curOrientation][curFrameIndex].Rect;
			}
		}


		/// <summary>
		/// Gets the current frame's offset
		/// </summary>
		public Vector2 FrameOffset
		{
			get
			{
				return sprite.Frame[curSet, curAnimation, curOrientation][curFrameIndex].Anchor;
			}
		}


		/// <summary>
		/// Updates the animator, advancing the frame when necessary
		/// </summary>
		/// <param name="gameTime">The GameTime instance</param>
		public void Update(GameTime gameTime)
		{
			Update(gameTime.ElapsedGameTime);
		}


		/// <summary>
		/// Updates the animator, advancing the frame when necessary
		/// </summary>
		/// <param name="elapsedGameTime">The elapsed game time</param>
		public void Update(TimeSpan elapsedGameTime)
		{
			double millisecondsBetweenFrames = 1000.0 / (sprite.Animation[curAnimation].FPS * sprite.Set[curSet].FPSMultiplier);

			timeSinceLastFrame = timeSinceLastFrame.Add(elapsedGameTime);
			while (timeSinceLastFrame.TotalMilliseconds > millisecondsBetweenFrames)
			{
				AdvanceFrame();
				timeSinceLastFrame = timeSinceLastFrame.Subtract(new TimeSpan(0, 0, 0, 0, (int)millisecondsBetweenFrames));
			}
		}


		/// <summary>
		/// Advance to the next frame
		/// </summary>
		public void AdvanceFrame()
		{
			curFrameIndex++;
			if (curFrameIndex >= sprite.Frame[curSet, curAnimation, curOrientation].Length)
			{
				// We have reached the end, start the "next animation".
				// Note: The frame index will automatically be reset
				CurrentAnimation = sprite.Animation[curAnimation].NextAnimationName;
			}
		}
	}
}
